package uk.me.dewi.net.rest;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.URI;

import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.ProtocolException;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.RedirectHandler;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.protocol.HttpContext;

import uk.me.dewi.appenin.android.dao.rest.AppeninHttpRequestFactory;
import android.util.Log;

public class RestClient {

    private AppeninHttpRequestFactory requestFactory;
    private DefaultHttpClient httpClient;
    
    
    public RestClient(AppeninHttpRequestFactory requestFactory){
        httpClient = new DefaultHttpClient();
        
        // disable redirects (handle manually below)
        httpClient.setRedirectHandler(new RedirectHandler(){
            public URI getLocationURI(HttpResponse response, HttpContext context) throws ProtocolException {
                return null;
            }

            public boolean isRedirectRequested(HttpResponse response, HttpContext context) {
                return false;
            }});
        
        this.requestFactory = requestFactory;
    }
    
    public Object get(String path, ResponseHandler responseHandler) throws RestException{
        return this.sendRequest(requestFactory.newHttpGet(path), responseHandler);
    }
    
    public Object put(String path, String data, ResponseHandler responseHandler) throws RestException{
        HttpPut put = requestFactory.newHttpPut(path);
        try {
            put.setEntity(new StringEntity(data));
        } catch (UnsupportedEncodingException e) {
            throw new RestException("Failed to encode request data", e);
        }
        return this.sendRequest(put, responseHandler);
    }

    public Object post(String path, String data, ResponseHandler responseHandler) throws RestException{
        HttpPost post = requestFactory.newHttpPost(path);
        try {
            post.setEntity(new StringEntity(data));
        } catch (UnsupportedEncodingException e) {
            throw new RestException("Failed to encode request data", e);
        }
        return this.sendRequest(post, responseHandler);
    }
    
    public Object delete(String path, ResponseHandler responseHandler) throws RestException{
        return this.sendRequest(requestFactory.newHttpDelete(path), responseHandler);
    }
    
    private Object sendRequest(HttpRequestBase request, ResponseHandler responseHandler) throws RestException{
        HttpResponse response;
        InputStream in = null;
        try {
            Log.d("RestClient", request.getMethod()+" "+request.getURI());
            response = httpClient.execute(request);
 
            switch(response.getStatusLine().getStatusCode()){
            case HttpStatus.SC_OK: break;
            case HttpStatus.SC_CREATED: break;
            case HttpStatus.SC_ACCEPTED: break;
            case HttpStatus.SC_NO_CONTENT: break;
            case HttpStatus.SC_MOVED_TEMPORARILY:
            case HttpStatus.SC_MOVED_PERMANENTLY:
                return handleRedirect(request, response, responseHandler);
            default:
                throw new RestException(request, response.getStatusLine().getReasonPhrase());
            }
                        
            HttpEntity entity = response.getEntity();
            if (entity != null) {
                in = entity.getContent();
                String result = readInputStream(in);
                Log.d("RestClient", result);
                try{
                    return responseHandler.handleResponse(result);
                }catch(Exception e){
                    throw new RestException("Failed to handle response", request, e);
                }
            }
            throw new RestException("No response entity");
        } catch (ClientProtocolException e) {
            throw new RestException(request, e);
        } catch (IOException e) {
            throw new RestException("Network Problem", request, e);
        } finally {
            if(in != null){
                try {
                    in.close();
                } catch (IOException e) {
                    throw new RestException("Failed to close stream", request, e);
                }
            }
        }
    }

    private Object handleRedirect(HttpRequestBase request, HttpResponse response,
            ResponseHandler responseHandler) throws RestException {
        Header locationHeader = response.getFirstHeader("location");
        if (locationHeader == null) {
            throw new RestException("Invalid Redirect Location");
        }
        String redirectLocation = locationHeader.getValue();
        System.out.println("Redirecting to "+redirectLocation);
        HttpGet redirectGet = new HttpGet(redirectLocation);
        redirectGet.setHeader(request.getFirstHeader("Accept"));
        redirectGet.setHeader(request.getFirstHeader("Content-type"));
        return this.sendRequest(redirectGet, responseHandler);
    }
    
    private String readInputStream(InputStream in) throws RestException {
        BufferedReader reader = new BufferedReader(new InputStreamReader(in));
        StringBuilder sb = new StringBuilder();
 
        String line = null;
        try {
            while((line = reader.readLine()) != null) {
                sb.append(line + "\n");
            }
        } catch (IOException e) {
            throw new RestException("Failed to load stream", e);
        } finally {
            try {
                in.close();
            } catch (IOException e) {
                throw new RestException("Failed to close stream", e);
            }
        }
        return sb.toString();
    }
    
}
